/*
 * Advanced Methods in Programming 
 * Final Project: Pizza-on-line
 * 
 *   
 *   @author 309170249 317335214
 */
package server;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.Semaphore;

import mutual.Order;
import mutual.RMIContracts;
import mutual.absClient;
import mutual.absServer;
import mutual.RMIContracts.Stages;

/* pizzaServer class */

public class pizzaServer extends UnicastRemoteObject implements absServer {
	/**
	 * pizza class implemented as a pizzaServer's thread 
	 * 
	 */
	private class pizza implements Runnable {
		private absClient client;
		private Order order;
		/**
		 * Notifies pizzaClient about a new status of clients's order 
		 * 
		 * @param stage
		 * 				 new status of a client's order
		 */
		private boolean notifyClient(Stages stage) {
			try {
				client.notifyStage(stage);
				return true;
			} catch (RemoteException e) {
				System.out.println("Client lost...");
			} catch (Exception e) {
				System.out.println("Should not get here!");
			}
			return false;
		}

		private pizza() { // hiding default ctor
		}
		/**
		 * Pizza ctor 
		 * 
		 * @param client
		 * 				 pizzaClient
		 * @param order
		 * 				order of pizzaClient
		 */
		public pizza(absClient client, Order order) {
			this.client = client;
			this.order = order;
		}
		/**
		 * Doing the job of pizza thread :
		 * 1) preparation
		 * 2) baking
		 * 3) delivery
		 * 
		 * @param none
		 * 
		 */
		public void run() {

			notifyClient(Stages.ACCEPTED);
			prepare();
			notifyClient(Stages.WAITING);
			bake();
			notifyClient(Stages.INDELIVERY);
			deliver();
			notifyClient(Stages.DONE);

		}
		/**
		 * Prepares pizza before placing it into oven
		 * 
		 * @param none
		 */
		private void prepare() {
			delay(stageLength);
		}
		/**
		 * Places pizza in the oven & bakes it
		 * semaphore "oven" is used for synchronization 
		 * Oven is able to bake at most 3 pizzas simultaneously 
		 * 
		 * @param none
		 */
		private void bake() {

			try {
				oven.acquire();
				System.out.println();
				synchronized (structuralLock) {
					runningOrders.remove(order.getOrderId());
					
					// handful of realism - just to see that the order
					// could have changed, while waiting
					for (String extra : order.getExtras()) {
						System.out.println(extra);
					}

					ordersArchive.put(order.getOrderId(), order);
				}

				notifyClient(Stages.INOVEN);
				delay(stageLength);
				oven.release();
			} catch (InterruptedException e) {
				System.out.println("The oven is broken");
			}

		}
		/**
		 * Delivers pizza to a pizzaClent that ordered it
		 * 
		 * @param none
		 */
		private void deliver() {
			delay(-1000);
		}
		/**
		 * Auxiliary function to facilitate actions with pizza
		 * 
		 * @param millis
		 * 					number of milliseconds to sleep
		 * 					negative number - random number between [millis,6*millis]
		 */
		private void delay(int millis) {
			try {
				if (millis > 0)
					Thread.sleep(millis);
				else
					Thread.sleep((1 + new Random().nextInt(6)) * (-1 * millis));
			} catch (InterruptedException e) {
				System.out.println("Exception while in sleep");
			}
		}

	}

	private static Registry registry = null;
	private static final long serialVersionUID = -4396056964351868324L;
	private final static long security = 317335214309170249L;

	private static HashMap<String, Order> ordersArchive;
	private static HashMap<String, Order> runningOrders;

	private static Semaphore oven;
	private static final int placeInOven = 3;
	private static final int stageLength = 5000;
	private static Integer serials = 0;
	private static Integer structuralLock = 0;
	/**
	 * pizzaServer ctor
	 * 
	 * @param none
	 * @throws RemoteException
	 * 							pizzaServer couldn't locate registry
	 */
	public pizzaServer() throws RemoteException {

		if (registry == null)
			try {
				registry = LocateRegistry
						.createRegistry(RMIContracts.serverPort);
			} catch (RemoteException e) {
				System.out.println("Alas, server couldn't locate registry");
			}

		RMIContracts.registerServer(registry,
				RMIContracts.serverName, this);

		ordersArchive = new HashMap<String, Order>();
		runningOrders = new HashMap<String, Order>();
		oven = new Semaphore(placeInOven);
	}
	/**
	 * Places new order in orders HashMap
	 * if pizza of this order is not in oven yet :  updates toppings 
	 * 
	 * @param order 
	 * 				order to proceed
	 * 				
	 */
	public void order(Order order) {
		if (order.isNew())
			newOrder(order);
		else
			tryUpdateOrder(order);
	}
	/**
	 * Auxiliary function for pizzaServer order toppings' update
	 * 
	 * @param order 
	 * 				order to update
	 * 				
	 */
	private void tryUpdateOrder(Order order) {
		boolean isActive = true;
		String name = order.getClientName();

		synchronized (structuralLock) {
			isActive = runningOrders.containsKey(order.getOrderId());
			if (isActive) // update order
				runningOrders.get(order.getOrderId()).setExtras(
						order.getExtras());
		}
		if (isActive)
			return;
		// else send can't update signal
		try {

			absClient client = (absClient) RMIContracts
					.getServer(name);
			client.notifyStage(Stages.CANTUPDATE);
		} catch (RemoteException e) {
			System.out.println("Problems while connecting to client");
		}

	}
	/**
	 * Kills the server in the most delicate manner
	 * 				
	 */
	private void die() {
		System.out.println("Pizza offline");
		System.exit(0);
	}
	/**
	 * Starts new order as a new thread
	 * putting new order into runningOrders HashMap
	 * 
	 * @param order 
	 * 				a new order to process
	 * 				
	 */
	private void newOrder(Order order) {

		String name = order.getClientName();

		absClient client = (absClient) RMIContracts
				.getServer(name);
		String id = getId();
		order.setOrderId(id, security);
		runningOrders.put(id, order);
		try {
			client.orderDetails(order);
		} catch (RemoteException e) {
			System.out.println("Problems while connecting to client");
		}
		new Thread(new pizza(client, order)).start();
	}
	/**
	 * An old order query from pizzaClient
	 * 
	 * @param clientName 
	 * 						a name of a pizzaClent
	 * @param id
	 * 						order's id number
	 * 				
	 */
	public void getOrder(String clientName, String id) {
		// remote killer for the server
		if (id.equals(RMIContracts.GrimReaper))
			die();

		Order order = null;
		synchronized (structuralLock) {
			order = ordersArchive.get(id);
		}

		// the order must exist, be already delivered and belong to querying
		// client
		order = (null == order || !order.getClientName().equals(clientName)) ? null
				: order;
		absClient client = (absClient) RMIContracts
				.getServer(clientName);
		try {
			client.orderDetails(order);
		} catch (RemoteException e) {
			System.out.println("Problems while connecting to client");
		}

	}
	/**
	 * Assigns a new id for a new order  
	 * synchronized to avoid simultaneous instances of orders to share the same id
	 * 
	 * @return a new order's id number
	 * 				
	 */
	private static synchronized String getId() {
		serials++;
		return serials.toString();
	}
	/**
	 * main function for a pizzaServer
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			new pizzaServer();
			System.out.println("Pizza is online");
		} catch (RemoteException e) {
			System.out.println("Could not create server");
			System.exit(0);
		}
	}

}
